// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: lucasyoo

#include "coin2/strategy/order_executor/layering_executor2.h"

namespace coin2::strategy::order_executor {

LayeringExecutor2::LayeringExecutor2(
    const PassiveExecutorConfig& config,
    IOrderSubsystem* os,
    std::optional<int64_t> timestamp,
    const TopBook* book,
    GetProductHolderFunc get_product_holder,
    std::optional<double> lot_size_quote,
    const bool verbose)
    : LayeringExecutor(config, os, timestamp, book, get_product_holder, lot_size_quote, verbose) {}

std::vector<LayeringLevels::Level> LayeringExecutor2::GetMostDesiredModification(
    LayeringLevels layering_levels,
    int64_t timestamp,
    int sign,
    const OrderList& wo_list,
    LayeringExecutor2* layering_executor) {
  double level_bp = 2;
  const auto& levels = layering_levels.levels;
  if (levels.size() >= 2) {
    level_bp =
        1e4 * (levels[1].price - levels[0].price) / (0.5 * (levels[1].price + levels[0].price));
    level_bp = fabs(level_bp);
  }

  LogAsIsToBe(timestamp, sign, wo_list, levels);

  auto woit = wo_list.cbegin();
  auto lvit = levels.cbegin();

  std::vector<LayeringLevels::Level> cancel_levels{};
  OrderList max_outside_orders;
  for (; woit != wo_list.cend() || lvit != levels.cend();) {
    auto comp = LayeringLevels::TIE;
    if (woit != wo_list.cend() && lvit != levels.cend()) {
      double tolerance_bp = 0;
      if (woit == wo_list.cbegin() || lvit == levels.cbegin()) {
        tolerance_bp = 2;
      } else {
        tolerance_bp = level_bp;
      }
      comp = LayeringLevels::ComparePrice((*woit)->order_price(), lvit->price, tolerance_bp, sign);
      if (verbose_) SPDLOG_INFO("{} vs {} : {}", (*woit)->order_price(), lvit->price, comp);
    }
    if (woit == wo_list.cend() || comp == LayeringLevels::OUTSIDE) {
      if (cancel_levels.size() != 0) return cancel_levels;
      CHECK_EQ(lvit->opertype, LayeringLevels::POST);
      if (!max_outside_orders.empty() &&
          lvit->price * sign > max_outside_orders.back()->order_price() * sign) {
        cancel_levels.push_back(
            LayeringLevels::Level::ConstructCancelLevel(max_outside_orders.back()));
        return cancel_levels;
      } else if (wo_list.size() >= levels.size()) {  // Full but it tries to POST -> Cancel Most
                                                     // Outside Order
        cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(wo_list.back()));
        return cancel_levels;
      } else {
        return std::vector<LayeringLevels::Level>{*lvit};
      }
    } else if (lvit == levels.cend() || comp == LayeringLevels::INSIDE) {
      cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(*woit));
      if (levels.empty() ||
          (!levels.empty() &&
           levels.cbegin()->price * sign < (*woit)->order_price() * sign)) {  // emergency cancels
        if (woit != wo_list.cend()) max_outside_orders.push_back(*woit);
        ++woit;
      } else {
        return cancel_levels;
      }
    } else if (comp == LayeringLevels::TIE) {
      if (!cancel_levels.empty()) return cancel_levels;
      if (woit != wo_list.cend()) max_outside_orders.push_back(*woit);
      ++lvit;
      ++woit;
    }
  }
  return cancel_levels;
}

void LayeringExecutor2::ManageSideOrders(int64_t timestamp, int sign, LayeringLevels levels) {
  if (!wo_) {
    SPDLOG_INFO_EVERY_NS(60e9, timestamp, "wo_ not ready");
    return;
  }
  if (!is_wo_ordered_) UpdateOrders(timestamp);
  levels.ClipQty(GetMaximumWorkingQty(sign), GetLotSizeQuote(), verbose_);

  CHECK_EQ(levels.sign, sign);

  const auto& order_list = sign > 0 ? ordered_buy_list_ : ordered_sell_list_;

  auto opers = this->GetMostDesiredModification(levels, timestamp, sign, order_list, this);
  for (const auto& oper : opers) {
    auto operkey = std::pair<int, LayeringLevels::OperationType>(sign, oper.opertype);
    if (oper.opertype == LayeringLevels::POST &&
        prev_oper_ts_[operkey] + order_update_period_ < timestamp) {
      SPDLOG_INFO("POST) sgn:{} {} @{}", sign, oper.qty, oper.price);
      ExecuteOperation(timestamp, sign, oper);
      prev_oper_ts_[operkey] = timestamp;
    } else if (oper.opertype == LayeringLevels::CANCEL) {
      ExecuteOperation(timestamp, sign, oper);
    }
  }
}

void LayeringExecutor2::ManageRisk(int64_t timestamp) {
  if (!wo_) {
    SPDLOG_INFO_EVERY_NS(60e9, timestamp, "wo_ not ready");
    return;
  }
  if (this->config().has_max_posting_period() && this->config().has_order_update_period()) {
    const auto& cancel_executor = this->GetCancelExecutor();
    cancel_executor->Clear();
    cancel_executor->CancelOldOrders(timestamp, timestamp - this->config().max_posting_period());
    cancel_executor->RemoveDuplicates();

    for (const auto& order_id : cancel_executor->GetCancelBuyList()) {
      if (timestamp - prev_buy_cancel_ts_ > order_update_period_) {
        os_->gateway()->CancelOrder(order_id);
        prev_buy_cancel_ts_ = timestamp;
        break;
      }
    }
    for (const auto& order_id : cancel_executor->GetCancelSellList()) {
      if (timestamp - prev_sell_cancel_ts_ > order_update_period_) {
        os_->gateway()->CancelOrder(order_id);
        prev_sell_cancel_ts_ = timestamp;
        break;
      }
    }
  }
}

int LayeringExecutor2::GetBuyWorkingOrderCount() { return wo_->GetBuyWorkingOrders().size(); }

int LayeringExecutor2::GetSellWorkingOrderCount() { return wo_->GetSellWorkingOrders().size(); }

std::vector<int64_t> LayeringExecutor2::GetAllOrderIds() {
  std::vector<int64_t> proc_order_ids;
  for (const auto& order : wo_->GetBuyWorkingOrders()) {
    proc_order_ids.push_back(order->proc_order_id());
  }
  for (const auto& order : wo_->GetSellWorkingOrders()) {
    proc_order_ids.push_back(order->proc_order_id());
  }
  return proc_order_ids;
}

std::vector<std::pair<double, double>> LayeringExecutor2::GetBuyWorkingOrderPairs() {
  std::vector<std::pair<double, double>> wop;
  for (const auto& order : wo_->GetBuyWorkingOrders()) {
    wop.push_back({order->order_price(), order->working_qty()});
  }
  return wop;
}

std::vector<std::pair<double, double>> LayeringExecutor2::GetSellWorkingOrderPairs() {
  std::vector<std::pair<double, double>> wop;
  for (const auto& order : wo_->GetSellWorkingOrders()) {
    wop.push_back({order->order_price(), order->working_qty()});
  }
  return wop;
}

}  // namespace coin2::strategy::order_executor
